package in.co.codedoc.framework;

import in.co.codedoc.cg.annotations.HasProperties;
import in.co.codedoc.cg.annotations.IntType;
import in.co.codedoc.cg.annotations.IsAnIOCComponent;
import in.co.codedoc.cg.annotations.IsMappedToJSON;
import in.co.codedoc.cg.annotations.Property;
import in.co.codedoc.config.Configurable;
import in.co.codedoc.config.ConfigurationUtil;
import in.co.codedoc.config.Configurator;
import in.co.codedoc.encode.URIEncoding;
import in.co.codedoc.encrypt.Signer;
import in.co.codedoc.json.JSONUtil;
import in.co.codedoc.json.JSONValue;
import in.co.codedoc.user.Authorization;
import in.co.codedoc.util.ObjectUtil;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@IsAnIOCComponent
@IsMappedToJSON
@HasProperties
({
	@Property(name="authCookieLifespan",datatype=IntType.class,defaultValue="1800"),
	@Property(name="authCookieName",datatype=String.class,defaultValue="\"authorization\""),
	@Property(name="uploadSizeLimit",datatype=IntType.class,defaultValue="100 * 1024 * 1024"),
	@Property(name="uploadTempDirectory",datatype=String.class)
})
public class ServiceInvoker
	extends ServiceInvokerBase
	implements Configurable
{
	public ServiceInvoker()
	{
		
	}
	
	public ServiceInvoker(Configurator configurator)
	{
		
	}
	
	@Override
	public void Configure(JSONValue jsonData)
	{
		ConfigurationUtil.ConfigureUsingProperties(this, jsonData);
	}

	@Override
	public String GetConfigSectionName()
	{
		return "ServiceInvoker";
	}
	
	@SuppressWarnings("unchecked")
	public Object InvokeService(Object object, Method method,
			Object[] arguments, HttpServletRequest request,
			HttpServletResponse response, org.apache.log4j.Logger logger)
	{
		try
		{
			if (logger.isDebugEnabled())
			{
				StringBuffer buffer = new StringBuffer();
				if (request.getCookies() != null)
				{
					buffer.append("\nCookies: ");
					for (Cookie cookie : request.getCookies())
					{
						buffer.append('\n');
						buffer.append('\t');
						buffer.append(cookie.getName() + ":"
								+ cookie.getValue());
					}
				}
				buffer
						.append("\nParameters(ignore the outermost [] - its an artifact of handling multiple param values): ");

				Map pm = request.getParameterMap();
				int i = 0;
				for (Object pn : pm.keySet())
				{
					Object value = pm.get(pn);
					
					if(pn == null || value == null)
					{
						buffer.append("<Null " + ((pn == null) ? " property name" : (" value for " + pn)) + ">");
						continue;
					}
					
					if (i > 0)
					{
						buffer.append(",");
					}
					
					buffer.append(URIEncoding.Decode(ObjectUtil.toString(pn))
							+ "=>"
							+ URIEncoding.Decode(ObjectUtil.toString(value)));
					i++;
				}

				logger.debug(buffer.toString());
			}

			Authorization.SetCurrentAuthorization((Authorization) null);

			String paramsAuthData = null;
			String cookiesAuthData = null;
			
			Map pm = request.getParameterMap();
			if(pm.containsKey(GetAuthCookieName()))
			{
				Object value = pm.get(GetAuthCookieName());
				
				if(value.getClass().isArray())
				{
					Object[] array = (Object[])value;
					value = null;
					for(int i = 0;(value == null) && (i< array.length);i++)
					{
						value = array[i];
					}
				}
				
				if(value != null)
				{
					paramsAuthData = URIEncoding.Decode(ObjectUtil.toString(value));
				}
			}
			
			if(request.getCookies() != null)
			{
				for (Cookie cookie : request.getCookies())
				{
					if (cookie.getName().equals(GetAuthCookieName()))
					{
						cookiesAuthData = URIEncoding.Decode(cookie.getValue());
						break;
					}
				}
			}			
			
			if(((paramsAuthData == null) || !ConsumeAuthData(paramsAuthData, logger)) && (cookiesAuthData != null))
			{
				ConsumeAuthData(cookiesAuthData, logger);
			}
			
			return method.invoke(object, arguments);
		}
		catch(Throwable th)
		{
			throw new RuntimeException("Failed to InvokeService:" + th.getMessage(),th);
		}
	}

	private boolean ConsumeAuthData(String authData,org.apache.log4j.Logger logger)
	{
		int firstHyphen = authData.indexOf('-');
		int secondHyphen = authData.indexOf('-', firstHyphen + 1);

		if ((firstHyphen >= 0) && (secondHyphen >= 0))
		{
			String jsonEncodedData = authData.substring(0,
					firstHyphen);
			long validUntil = Long.parseLong(authData.substring(
					firstHyphen + 1, secondHyphen));
			String signature = authData
					.substring(secondHyphen + 1);

			if (Signer.Validate(signature, authData.substring(0,
					secondHyphen).getBytes(), validUntil))
			{
				if (logger.isDebugEnabled())
				{
					logger.debug("Valid authorization cookie '"
							+ authData + "'");
				}
				Authorization auth = (Authorization) JSONUtil
						.Decode(JSONValue
								.Decode(jsonEncodedData),
								Authorization.class
										.getCanonicalName());
				if (!(auth == null || auth.GetId() == null))
				{
					Authorization.SetCurrentAuthorization(auth);
				}
			}
			else
			{
				if (logger.isDebugEnabled())
				{
					logger
							.debug("Expired/Invalid signature on '"
									+ authData + "'");
				}
			}
		}
		else
		{
			logger
					.error("Incorrect formatting of authorization cookie: '"
							+ authData + "'");
		}
		
		return Authorization.GetCurrentAuthorization() != null;
	}
	
	public void AddCookies(HttpServletResponse response)
	{
		for(Cookie cookie:GetCookies())
		{
			if(Logger.logger.isDebugEnabled())
			{
				Logger.logger.debug
				(
					"Setting response cookie(Name:Value:Age:Path):" + 
					cookie.getName() + ":" + cookie.getValue() + ":" +
					cookie.getMaxAge() + ":" + cookie.getPath()
				);
			}
			response.addCookie(cookie);
		}
	}
	
	public List<Cookie> GetCookies()
	{
		List<Cookie> cookies =  new ArrayList<Cookie>();

		cookies.add(MakeAuthorizationCookie());
		
		return cookies;
	}

	private Cookie MakeAuthorizationCookie()
	{
		Cookie cookie = null;
		
		if(Authorization.GetCurrentAuthorization() != null)
		{
			cookie = new Cookie
				(
					GetAuthCookieName(),
					Authorization.GetCurrentAuthorization().MakeAuthorizationCookieData(GetAuthCookieLifespan())
				);

			cookie.setMaxAge(GetAuthCookieLifespan());
		}
		else
		{
			cookie = new Cookie(GetAuthCookieName(),"expired");

			cookie.setMaxAge(0);
		}
		
		cookie.setPath("/");

		return cookie;
	}
}
